package com.example.server.db.dao

import com.example.commons.models.Article
import com.example.server.db.DatabaseFactory.dbQuery
import com.example.server.db.tables.Articles
import com.example.server.db.tables.Users
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq


interface ArticleDao {
    suspend fun allArticles(): List<Article>
    suspend fun page(page: Int = 1, size: Int = 30): List<Article>
    suspend fun article(id: Long): Article?
    suspend fun addNewArticle(article: Article): Article?
    suspend fun editArticle(article: Article): Boolean
    suspend fun deleteArticle(id: Long): Boolean
    suspend fun findById(id: Long): Article?

    suspend fun findWithUserById(id: Long): Article?
}


class ArticleDaoImpl : ArticleDao {

    override suspend fun allArticles(): List<Article> = dbQuery {
        Articles.selectAll().map(Articles::resultRowToArticle)
    }

    override suspend fun page(page: Int, size: Int): List<Article> = dbQuery {
        val p = if (page <= 1) page else 1
        Articles.selectAll().limit(size, ((p - 1) * size).toLong())
            .map(Articles::resultRowToArticle)
    }

    override suspend fun article(id: Long): Article? = dbQuery {
        Articles.select {
            Articles.id.eq(id)
        }
            .map(Articles::resultRowToArticle)
            .singleOrNull()
    }

    override suspend fun findById(id: Long): Article? = dbQuery {
        Articles.select {
            Articles.id.eq(id)
        }
            .map(Articles::resultRowToArticle)
            .singleOrNull()
    }

    override suspend fun findWithUserById(id: Long): Article? = dbQuery {
        Articles.leftJoin(Users) //slice(Articles.userId, Users.id)
            .select {
                Articles.id.eq(id)
            }.map {
                val a = Articles.resultRowToArticle(it)
                val u = Users.resultRowToUser(it)
                a.author = u
                a
            }.singleOrNull()
    }

    override suspend fun addNewArticle(article: Article): Article? = dbQuery {
        val insertStatement = Articles.insert {
            it[title] = article.title
            it[body] = article.body
            it[userId] = article.userId
        }
        insertStatement.resultedValues?.singleOrNull()?.let(Articles::resultRowToArticle)
    }

    override suspend fun editArticle(article: Article): Boolean = dbQuery {
        Articles.update({ Articles.id eq article.id }) {
            if (article.title.isNotBlank()) {
                it[title] = article.title
            }
            if (article.body.isNotBlank()) {
                it[body] = article.body
            }
            if (article.userId != null) {
                it[userId] = article.userId
            }
        } > 0
    }

    override suspend fun deleteArticle(id: Long): Boolean = dbQuery {
        Articles.deleteWhere { Articles.id eq id } > 0
    }

    companion object {

        @Volatile
        private var INSTANCE: ArticleDao? = null

        fun getInstance(): ArticleDao = INSTANCE ?: synchronized(this) {
            INSTANCE ?: ArticleDaoImpl().also { INSTANCE = it }
        }

    }
}

//val articleDao: ArticleDao = ArticleDaoImpl().apply {
//    runBlocking {
//        if(allArticles().isEmpty()) {
//            addNewArticle(
//                Article(0, 0, "The drive to develop!", "...it's what keeps me going.")
//            )
//        }
//    }
//}
